package com.gitee.booting.dspi;

import com.gitee.booting.balancer4j.Action;
import com.gitee.booting.balancer4j.Candidate;
import com.gitee.booting.balancer4j.QueueLoadBalancer;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.client.RestTemplate;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;

import static com.gitee.booting.dspi.Extensions.SLASH;

public class Extension implements InvocationHandler {
    private final RestTemplate template;
    private final DiscoveryClient discovery;
    private final QueueLoadBalancer balancer;
    private final String clazz;
    private final String service;
    private final String name;

    public Extension(RestTemplate template, DiscoveryClient discovery, QueueLoadBalancer balancer, Class<?> clazz, String service, String... names) {
        this.template = template;
        this.discovery = discovery;
        this.balancer = balancer;
        this.clazz = clazz.getName();
        this.service = service;
        this.name = null != names && names.length > 0 ? names[0] : Extensions.SERVICE_DEFAULT;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        List<Candidate<ServiceInstance, Object>> candidates = new ArrayList<>();
        for (ServiceInstance si : this.discovery.getInstances(this.service)) {
            if (si.getMetadata().containsKey(this.clazz)) {
                String services = si.getMetadata().get(this.clazz);
                for (String service : services.split(Extensions.COMMA)) {
                    final String[] kv = service.split(Extensions.SERVICE_NAME_SEPARATOR);
                    if (kv[0].equals(this.name) || Extensions.SERVICE_DEFAULT.equals(this.name)) {
                        candidates.add(Candidate.of(si,
                            i -> Collections.singletonMap(Extensions.PROVIDER, kv.length >= 2 ? kv[1] : kv[0])));
                        break;
                    }
                }
            }
        }
        this.balancer.build(() -> candidates);
        final AtomicReference<Object> result = new AtomicReference<>();
        this.balancer.process(Action.<Candidate<ServiceInstance, Object>, Object>of(i ->
            this.template.postForObject(
                i.instance().getUri().toASCIIString() + Extensions.ENDPOINT
                    + SLASH + this.clazz
                    + SLASH + i.meta().get(Extensions.PROVIDER)
                    + SLASH + method.getName()
                    + SLASH + types(method.getParameterTypes()),
                args, method.getReturnType())), result::set);
        return result.get();
    }

    private static String types(Class<?>[] parameterTypes) {
        if (null == parameterTypes || parameterTypes.length == 0) {
            return Extensions.MINUS;
        }
        StringBuilder builder = new StringBuilder();
        for (Class<?> c : parameterTypes) {
            builder.append(c.getName()).append(Extensions.COMMA);
        }
        return builder.substring(0, builder.length() - 1);
    }
}
